#define LOG_TAG "SpiFLash"
#include <COMMON_HEADER/common_header.h>
/*
 * Copyright (c) 2016-2020, Chengdu RST technology co., ltd
 *
 * Change Logs:
 * Date           Author       Notes
 * 2020-12-15     shijiabao       the first version
 */

/* SPI 设备初始化注册,此函数由系统自动调用  */
static int rt_hw_spi_flash_init(void)
{
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /* spi10 表示挂载在 spi1 总线上的 0 号设备,PC0是片选，这一步就可以将从设备挂在到总线中。 */
    rt_hw_spi_device_attach(SPI_BUS_NAME, W25Q64_SPI_DEVICE_NAME, GPIOC, GPIO_PIN_0);

    /* 注册块设备，这一步可以将外部flash抽象为系统的块设备  */
    if (RT_NULL == rt_sfud_flash_probe(W25Q64_BLOCK_DEVICE_NAME, W25Q64_SPI_DEVICE_NAME));
    {
        return -RT_ERROR;
    };

    return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_spi_flash_init);

void spi_flash_init(void)
{
    rt_hw_spi_flash_init();
    /* 查找 spi 设备获取设备句柄 */
    spi_dev_w25q = (struct rt_spi_device *)rt_device_find(W25Q64_SPI_DEVICE_NAME);
    if (!spi_dev_w25q)
    {
        LOG_E("Can't find %s device!\n", W25Q64_SPI_DEVICE_NAME);
    }
    /*配置SPI通讯相关参数  */
    struct rt_spi_configuration cfg;
    cfg.data_width = 8;                                     /* 数据宽度 */
    cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;  /* 主从模式、时钟极性和时钟相位、数据传输顺序是MSB位在前还是LSB位在前 */
    cfg.max_hz = 20 * 1000 *1000;                           /* 20M */

    rt_spi_configure(spi_dev_w25q, &cfg);
}

/**
 * spi flash 写单个字节指令
  *  函数功能：spi flash 写单个字节指令
  * 注意：使用该函数需要手动片选设备
 */
uint8_t spi_flash_send_byte(uint8_t byte_data)
{
    struct rt_spi_message msg1;

    uint8_t send_data[1] = {0};
    uint8_t recv_data[1] = {0};
    uint8_t return_data = 0;

    send_data[0] = byte_data;

    msg1.send_buf   = send_data;
    msg1.recv_buf   = recv_data;
    msg1.length     = 1;
    msg1.cs_take    = 0;
    msg1.cs_release = 0;
    msg1.next       = RT_NULL;

    rt_spi_transfer_message(spi_dev_w25q, &msg1);
    return_data = recv_data[0];
    return return_data;
}

/**
  *  等待flash数据写完
  *  函数功能：等待flash数据写完
 */
void spi_flash_wait_write_end(void)
{
    uint8_t FLASH_Status = 0;
    /* 选中片选 */
    rt_spi_take(spi_dev_w25q);
    /* 发送 读进寄存器命令 */
    spi_flash_send_byte(W25X_ReadStatusReg);
    /* 若FLASH忙碌，则等待 */
    do
    {
        /* 读取FLASH芯片的状态寄存器 */
        FLASH_Status = spi_flash_send_byte(Dummy_Byte);
    }while((FLASH_Status & WIP_Flag) == 1); /* 正在写入标志 */

    /* 释放片选 */
    rt_spi_release(spi_dev_w25q);
}

/**
 * spi flash 写使能命令
  *  函数功能：发送spi flash 写使能命令
 */
static void spi_flash_write_enable(void)
{
    /* 选中片选 */
    rt_spi_take(spi_dev_w25q);

    spi_flash_send_byte(W25X_WriteEnable);

    /* 释放片选 */
    rt_spi_release(spi_dev_w25q);
}

/**
 * spi flash 扇区擦除指令
  *  函数功能：扇区擦除指令,一次擦除4KB
 *  @param addr 要擦除扇区的地址
 */
void spi_flash_sector_erase(uint32_t SectorAddr)
{
    /* 发送FLASH写使能命令 */
    spi_flash_write_enable();
    spi_flash_wait_write_end();
    /* 选中片选 */
    rt_spi_take(spi_dev_w25q);
    /* 发送扇区擦除指令*/
    spi_flash_send_byte(W25X_SectorErase);
    /*发送擦除扇区地址的高位*/
    spi_flash_send_byte((SectorAddr & 0xFF0000) >> 16);
    /* 发送擦除扇区地址的中位 */
    spi_flash_send_byte((SectorAddr & 0xFF00) >> 8);
    /* 发送擦除扇区地址的低位 */
    spi_flash_send_byte(SectorAddr & 0xFF);
    /* 释放片选 */
    rt_spi_release(spi_dev_w25q);
    /* 等待擦除完毕*/
    spi_flash_wait_write_end();
}

/**
  * @brief  对FLASH按页写入数据，调用本函数写入数据前需要先擦除扇区
  * @param  pBuffer，要写入数据的指针
  * @param WriteAddr，写入地址
  * @param  NumByteToWrite，写入数据长度，必须小于等于SPI_FLASH_PerWritePageSize
  * @retval 无
  */
void spi_flash_page_write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    /* 发送FLASH写使能命令 */
    spi_flash_write_enable();
    /* 选中片选 */
    rt_spi_take(spi_dev_w25q);
    /* 写页写指令*/
    spi_flash_send_byte(W25X_PageProgram);
    /*发送写地址的高位*/
    spi_flash_send_byte((WriteAddr & 0xFF0000) >> 16);
    /*发送写地址的中位*/
    spi_flash_send_byte((WriteAddr & 0xFF00) >> 8);
    /*发送写地址的低位*/
    spi_flash_send_byte(WriteAddr & 0xFF);

    if(NumByteToWrite > SPI_FLASH_PerWritePageSize)
    {
         NumByteToWrite = SPI_FLASH_PerWritePageSize;
         LOG_E("SPI_FLASH_PageWrite too large!");
    }
    /* 写入数据*/
    while (NumByteToWrite--)
    {
        /* 发送当前要写入的字节数据 */
        spi_flash_send_byte(*pBuffer);
        /* 指向下一字节数据 */
        pBuffer++;
    }

    /* 停止信号 FLASH: CS 高电平 */
    rt_spi_release(spi_dev_w25q);

    /* 等待写入完毕*/
    spi_flash_wait_write_end();
}
/**
 * spi flash 写flash
  *  函数功能：写flash
 * @param  pBuffer，要写入数据的指针
 * @param  WriteAddr，写入地址
 * @param  NumByteToWrite，写入数据长度
 */
void spi_flash_buffer_write(uint8_t *pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
    uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
    /*mod运算求余，若writeAddr是SPI_FLASH_PageSize整数倍，运算结果Addr值为0*/
    Addr = WriteAddr % SPI_FLASH_PageSize;
    /*差count个数据值，刚好可以对齐到页地址*/
    count = SPI_FLASH_PageSize - Addr;
    /*计算出要写多少整数页*/
    NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
    /*mod运算求余，计算出剩余不满一页的字节数*/
    NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
    /* Addr=0,则WriteAddr 刚好按页对齐 aligned  */
      if (Addr == 0)
      {
            /* NumByteToWrite < SPI_FLASH_PageSize */
        if (NumOfPage == 0)
        {
            spi_flash_page_write(pBuffer, WriteAddr, NumByteToWrite);
        }
        else /* NumByteToWrite > SPI_FLASH_PageSize */
        {
                /*先把整数页都写了*/
          while (NumOfPage--)
          {
            spi_flash_page_write(pBuffer, WriteAddr, SPI_FLASH_PageSize);
            WriteAddr +=  SPI_FLASH_PageSize;
            pBuffer += SPI_FLASH_PageSize;
          }
                /*若有多余的不满一页的数据，把它写完*/
          spi_flash_page_write(pBuffer, WriteAddr, NumOfSingle);
        }
      }
        /* 若地址与 SPI_FLASH_PageSize 不对齐  */
      else
      {
            /* NumByteToWrite < SPI_FLASH_PageSize */
        if (NumOfPage == 0)
        {
                /*当前页剩余的count个位置比NumOfSingle小，一页写不完*/
          if (NumOfSingle > count)
          {
            temp = NumOfSingle - count;
                    /*先写满当前页*/
            spi_flash_page_write(pBuffer, WriteAddr, count);

            WriteAddr +=  count;
            pBuffer += count;
                    /*再写剩余的数据*/
            spi_flash_page_write(pBuffer, WriteAddr, temp);
          }
          else /*当前页剩余的count个位置能写完NumOfSingle个数据*/
          {
              spi_flash_page_write(pBuffer, WriteAddr, NumByteToWrite);
          }
        }
        else /* NumByteToWrite > SPI_FLASH_PageSize */
        {
                /*地址不对齐多出的count分开处理，不加入这个运算*/
          NumByteToWrite -= count;
          NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
          NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

                /* 先写完count个数据，为的是让下一次要写的地址对齐 */
          spi_flash_page_write(pBuffer, WriteAddr, count);

                /* 接下来就重复地址对齐的情况 */
          WriteAddr +=  count;
          pBuffer += count;
                /*把整数页都写了*/
          while (NumOfPage--)
          {
              spi_flash_page_write(pBuffer, WriteAddr, SPI_FLASH_PageSize);
            WriteAddr +=  SPI_FLASH_PageSize;
            pBuffer += SPI_FLASH_PageSize;
          }
                /*若有多余的不满一页的数据，把它写完*/
          if (NumOfSingle != 0)
          {
              spi_flash_page_write(pBuffer, WriteAddr, NumOfSingle);
          }
        }
      }
}

/**
  * @brief  读取FLASH数据
  * @param  pBuffer，存储读出数据的指针
  * @param   ReadAddr，读取地址
  * @param   NumByteToRead，读取数据长度
  * @retval 无
  */
void spi_flash_buffer_read(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
    /* 选中片选 */
    rt_spi_take(spi_dev_w25q);
    /* 发送 读 指令 */
    spi_flash_send_byte(W25X_ReadData);
    /* 发送 读 地址高位 */
    spi_flash_send_byte((ReadAddr & 0xFF0000) >> 16);
    /* 发送 读 地址中位 */
    spi_flash_send_byte((ReadAddr& 0xFF00) >> 8);
    /* 发送 读 地址低位 */
    spi_flash_send_byte(ReadAddr & 0xFF);

    /* 读取数据 */
    while (NumByteToRead--) /* while there is data to be read */
    {
        /* 读取一个字节*/
        *pBuffer = spi_flash_send_byte(Dummy_Byte);
        /* 指向下一个字节缓冲区 */
        pBuffer++;
    }

    /* 停止信号 FLASH: CS 高电平 */
    rt_spi_release(spi_dev_w25q);
}

/**
 * @brief  读取FLASH ID
 * @param  无
 * @retval FLASH ID
 */

uint32_t spi_flash_read_id(void)
{
  uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
  /* 选中片选 */
  rt_spi_take(spi_dev_w25q);
  /* 发送JEDEC指令，读取ID */
  spi_flash_send_byte(W25X_JedecDeviceID);
  /* 读取一个字节数据 */
  Temp0 = spi_flash_send_byte(Dummy_Byte);
  /* 读取一个字节数据 */
  Temp1 = spi_flash_send_byte(Dummy_Byte);
  /* 读取一个字节数据 */
  Temp2 = spi_flash_send_byte(Dummy_Byte);
  /* 停止信号 FLASH: CS 高电平 */
  rt_spi_release(spi_dev_w25q);
  /*把数据组合起来，作为函数的返回值*/
  Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
  return Temp;
}

//SPI FLASH读写测试例程
void spi1_test(void)
{
    spi_flash_init();
    uint32_t Temp =0;
    Temp = spi_flash_read_id();
    LOG_I("spi test,flash id = %x",Temp);
}



